#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <openssl/sha.h>

#include "benc.h"
#include "tools.h"

unsigned char		hash_str[20];		// there shoud be better way

int benc_metafile(char *buffer, int buffLen, benc *dict, unsigned char *hash)
{
	int		useLen = 0;

	if ( NULL == buffer || NULL == dict || NULL == hash ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( benc_dict(buffer, buffLen, &useLen, dict) < 0 ) {
		return -1;
	}

	memcpy(hash, hash_str, 20);

	return 0;
}

int benc_integer(char *buffer, int buffLen, int *useLen, benc *intg)
{
	int			 left = buffLen;
	char		*buff = NULL;

	if ( NULL == buffer || NULL == useLen || NULL == intg ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}

	buff = buffer;

	if ( *buffer != 'i' ) {
		xx_err("parse integer error!");
		return -1;
	}

	intg->type = BENC_INTEGER;

	while ( left ){
		buffer++;
		left--;

		if ( !buffer ) {
			xx_err("parse integer error!");
			return -1;
		}

		if ( !isdigit(*buffer) ) {
			if ( *buffer == 'e' ) {
				*buffer = '\0';
				left--;
				intg->integer = atoi(buff + 1);
				*useLen = buffLen - left;
//				xx_dbg("integer: %d\n", intg->integer);
				return 0;
			}
			else{
				xx_err("parse integer error!");
				return -1;
			}
		}
		continue;
	}
	return 0;

}

int benc_string(char *buffer, int buffLen, int *useLen, benc *str)
{
	char		*buff;
	int			 left = buffLen;
	int			 len  = 0; 

	if ( NULL == buffer || NULL == useLen || NULL == str ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}
	if ( !isdigit(*buffer) ) {
		xx_err("parse string error! -- unavaliable buffer");
		return -1;
	}
	buff = buffer;

	str->type = BENC_STRING;

	while ( left ){
		buffer++;
		left--;
		if ( !buffer ) {
			xx_err("parse string error!  -- buffer end");
			return -1;
		}

		if ( !isdigit(*buffer) ) {
			if ( *buffer == ':' ) {
				*buffer = '\0';
				buffer++;
				left--;

				len = atoi(buff);
				str->string = (char *)malloc(len + 1);
				if ( str->string == NULL ) {
					perror("calloc");
				}
				memcpy(str->string, buffer, len);
				str->string[len] = '\0';
				left -= len;
				*useLen = buffLen - left; 
//				xx_dbg("string: %s\n", str->string);
				break;
			}
			else{
				xx_err("parse string error! -- unavaliable string");
				return -1;
			}
		}

		continue;
	}
	return 0;
}

int benc_list(char *buffer, int buffLen, int *useLen, benc *list)
{
	int		left  = buffLen;
//	char	tag;
	int		ret   = 0;
	benc  **elems = NULL;

	if ( NULL == buffer || NULL == useLen || NULL == list ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( *buffer != 'l' ) {
		xx_err("parse list error!");
		return -1;
	}

	list->type = BENC_LIST;
	list->b_elems = (benc **)malloc(MAX_ELEM * sizeof(benc *));
	elems = list->b_elems;

	buffer++;
	left--;

	if ( !buffer ) {
		xx_err("parse list error!");
		return -1;
	}

	while ( left ){
		if ( !buffer ) {
			xx_err("parse list error!");
			return -1;
		}
		switch ( *buffer ){
			case 'i':
				*elems = (benc *)malloc(sizeof(benc));
				ret = benc_integer(buffer, left, useLen, *elems);
				break;
			case 'l':
				*elems = (benc *)malloc(sizeof(benc));
				ret = benc_list(buffer, left, useLen, *elems);
				break;
			case 'd':
				*elems  = (benc *)malloc(sizeof(benc));
				ret = benc_dict(buffer, left, useLen, *(elems));
				break;
			case 'e':
				*buffer = '\0';
				left--;
				*useLen = buffLen - left;
				return 0;
			default:
				if ( *buffer == '\0' ) {
					xx_err("parse list error! buffer is NULL");
					return -1;
				}
				if ( !isdigit(*buffer) ) {
					xx_err("--parse list error!");
//					xx_dbg("\t?? buffer: %s\n", buffer);
					return -1;
				}
				*elems = (benc *)malloc(sizeof(benc));
				ret = benc_string(buffer, left, useLen, *elems);
				break;
		}

		if ( ret == -1 ) {
			xx_dbg("I'am sorry\n");
			return -1;
		}
		left -= *useLen;
		buffer += *useLen;

		elems++;
		continue;
	}
#if 0
	/* *
	 *	In my opinion, the elements type in one list are same
	 *	so i write the code like below, not same with dict
	 * */
	tag = *buffer;
	switch ( tag ){							
		case 'e':		// empty list?
			return 0;
		case 'i':		// list of integers
		case 'l':		// list of lists
		case 'd':		// list of dicts
		default:		 
			while ( left && (*buffer != 'e')){
				*elems = (benc *)malloc(sizeof(benc));
				if ( tag == 'i' ){
					ret = benc_integer(buffer, left, useLen, *elems);
				}else if ( tag == 'l' ){
					ret = benc_list(buffer, left, useLen, *elems);
				}else if ( tag == 'd' ){
					ret = benc_dict(buffer, left, useLen, *elems);
				}else if ( isdigit(tag) ){ 	// list of strings
					ret = benc_string(buffer, left, useLen, *elems);	
				}else
					return -1;

				if ( ret != 0 ) {
					return -1;
				}

				elems++;

				left -= *useLen;
				buffer += *useLen;
				continue;
			}
			break;
	}
	if ( *buffer == 'e' ) {
		left--;
		*useLen = buffLen - left;
		return 0;
	}
	else{
		return -1;		// buffer does not contain a complete list
	}
#endif
	return 0;

}

int benc_dict(char *buffer, int buffLen, int *useLen, benc *dict)
{
	int					left	 = buffLen;
	int					ret		 = 0;
	int					bInfo	 = 0; 
	int					infoStar = 0;
	char			  * infoDict = NULL;
	benc			  **elems	 = NULL;

	if ( NULL == buffer || NULL == useLen || NULL == dict ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( *buffer != 'd' ) {
		xx_err("parse dict error!");
		return -1;
	}

	dict->type = BENC_DICT;
	dict->b_elems = (benc **)malloc(MAX_ELEM * sizeof(benc *));
	memset(dict->b_elems, 0, MAX_ELEM * sizeof(benc *));
	elems = dict->b_elems;

	buffer++;
	left--;

	*elems = (benc *)malloc(sizeof(benc));
	if ( 0 != benc_string(buffer, left, useLen, *elems) ) {
		xx_err("parse dict error!");
		return -1;
	}
//	xx_dbg("--: %s\n", (*elems)->string);

	elems++;

	left -= *useLen;
	buffer += *useLen;

	while ( left ){
		if ( !buffer ) {
			xx_err("parse dict error!");
			return -1;
		}
		switch ( *buffer ){
			case 'i':
				*elems = (benc *)malloc(sizeof(benc));
				ret = benc_integer(buffer, left, useLen, *elems);
				break;
			case 'l':
				*elems = (benc *)malloc(sizeof(benc));
				ret = benc_list(buffer, left, useLen, *elems);
				break;
			case 'd':
				*elems  = (benc *)malloc(sizeof(benc));
				if ( bInfo ) {
					infoDict = buffer;
					infoStar = *useLen;
				}
				ret = benc_dict(buffer, left, useLen, *(elems));
				break;
			case 'e':
				*buffer = '\0';
				left--;
				*useLen = buffLen - left;

				if ( bInfo && infoDict) {
					bInfo = 0;
					SHA1((unsigned char *)infoDict, (unsigned long)(*useLen - infoStar), hash_str);
				}

				return 0;
			default:
				if ( *buffer == '\0' ) {
					xx_err("parse dict error! buffer is NULL");
					return -1;
				}
				if ( !isdigit(*buffer) ) {
					xx_err("parse dict error!");
//					xx_dbg("\t?? buffer: %c\n", *buffer);
					return -1;
				}
				*elems = (benc *)malloc(sizeof(benc));
				ret = benc_string(buffer, left, useLen, *elems);
				if ( 0 == strncmp((*elems)->string, "info", 4) ) {
					xx_dbg("=================\n");
					bInfo = 1;
				}
				break;
		}

		if ( ret == -1 ) {
			xx_dbg("I'am sorry\n");
			return -1;
		}
		left -= *useLen;
		buffer += *useLen;

		elems++;
		continue;
	}
	return 0;
}

int get_benc_info(const char * const key, benc_type type, benc *dict, void *info)
{
	benc		**elems = NULL;

	if ( NULL == key || NULL == dict || NULL == info ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}

	elems = dict->b_elems;
	while ( *elems ){
		if ( BENC_STRING != (*elems)->type ){
			elems++;
			continue;
		}

		xx_dbg("key   :%s\n", key);
		xx_dbg("string:%s\n", (*elems)->string);

		if ( strcmp((*elems)->string, key) ) {
			elems++;
			continue;
		}
		elems++;
		if ( type != (*elems)->type ){
			return -1;
		}
		switch ( type ) {
			case BENC_INTEGER:
				*((int *)info) = (*elems)->integer;
				break;
			case BENC_STRING:
				strcpy(info, (*elems)->string);
				break;
			case BENC_LIST:
				*(benc ***)info = (*elems)->b_elems;
				break;
			default:
				break;
		}
		break;
	}

	return 0;
}

int free_string(benc *objStr)
{
	if ( NULL == objStr ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}
	
	if ( objStr->type != BENC_STRING || !(objStr->string)) {
		return -1;
	}
//	xx_dbg("freeing string: %s\n", objStr->string);

	free(objStr->string);

	return 0;
}

int free_list(benc *objList)
{
	benc		**elems = NULL;

	if ( NULL == objList ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}

//	xx_dbg("freeing list\n");
	elems = objList->b_elems;	
	while ( *elems ){
		if ( BENC_STRING == (*elems)->type ) {
			free_string(*elems);
		}
		else if ( BENC_LIST == (*elems)->type) {
			free_list(*elems);
		}
		else if ( BENC_DICT == (*elems)->type) {
			free_dict(*elems);
		}
		free(*elems);

		elems++;
	}

	if ( !objList->b_elems ) {
		free(objList->b_elems);
	}
//	xx_dbg("free list end\n");
	return 0;
}

int free_dict(benc *objDict)
{
	benc		**elems = NULL;

	if ( NULL == objDict ) {
		xx_dbg("Input para invalid\n");
		xx_assert(0);
		return -1;
	}

//	xx_dbg("freeing dict\n");
	elems = objDict->b_elems;	
	while ( *elems ){
		if ( BENC_STRING == (*elems)->type) {
			free_string(*elems);
		}
		else if ( BENC_LIST == (*elems)->type) {
			free_list(*elems);
		}
		else if ( BENC_DICT == (*elems)->type) {
			free_dict(*elems);
		}
		free(*elems);

		elems++;
	}

	if ( !objDict->b_elems ) 
		free(objDict->b_elems);

//	xx_dbg("free dict end\n");
	return 0;
}
